﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System;
using System.Reflection;
using LinFu.AOP.Interfaces;

namespace BrainTechLLC
{
	// RecordParametersWrapper is able to (1) record parameter and return values and/or 
	// (2) play back pre-recorded return values in lieu of original method execution
	public class RecordParametersWrapper : AroundMethodBase, IAroundInvoke
	{
		// Dummy RecordParametersAttribute associated with methods in ClassMethodsMockable-attribute marked classes
		private static readonly RecordParametersAttribute RecordParamForMocking = new RecordParametersAttribute(true, true);

		/// <summary>
		/// Substitutes a pre-recorded return value (if one is found) 
		/// in lieu of original method execution
		/// </summary>
		private void UseMockReturnValue(IInvocationContext context, Type t)
		{
			StoredParametersAndReturnValue mock = t.GetMockObject(context.TargetMethod.Name, context.Arguments);

			if (mock != null)
			{
				// Found a pre-recorded return value
				IModifiableType mod = (context.Target as IModifiableType);
				context.ExtraInfo = mod.IsInterceptionDisabled;
				mod.ExtraInfoAdditional = mock.ReturnValue;

				// Mark that original method execution should be skipped
				mod.IsInterceptionDisabled = true;

				if (mock.ReturnValue != null)
				{
					// If the recorded return object is identifiable as a pre-recorded return value,
					// mark it as such
					ICanIdentifyAsMockObject mockIndicatable = mock.ReturnValue as ICanIdentifyAsMockObject;

					if (mockIndicatable != null)
						mockIndicatable.IsMockReturnValue = true;
				}
			}
		}

		/// <summary>
		/// Re-enables original method execution
		/// </summary>		
		private void ReEnableMethodExecution(IInvocationContext context)
		{
			IModifiableType mod = (context.Target as IModifiableType);
			mod.IsInterceptionDisabled = Convert.ToBoolean(context.ExtraInfo);
			mod.ExtraInfoAdditional = null;
			context.ExtraInfo = null;
		}

		/// <summary>
		/// Records parameter and return values to the MockObjectRepository for later playback
		/// </summary>
		private void RecordParametersToMock(IInvocationContext context, Type t, RecordParametersAttribute attr, object returnValue)
		{
			MockObjectsForClass mockObjects = t.RetrieveMockObjectsForClass();
			if (!attr.OnlyWhenRecordingMocks || mockObjects.MockRecordingEnabled)
			{
				StoredParametersAndReturnValue found = t.RecordMockObjectReturnValue(context.TargetMethod.Name, context.Arguments, returnValue);
				found.PackageMockForStorage(true, true);
			}
		}

		protected override void Execute(IInvocationContext context, CutpointType when, object returnValue)
		{
			object thisObject = context.Target;
			Type t = thisObject.GetType();
			RecordParametersAttribute[] attrs = context.TargetMethod.ReadAttribute<RecordParametersAttribute>();
			DoNotMockMeAttribute[] attrsDoNotMock = context.TargetMethod.ReadAttribute<DoNotMockMeAttribute>();
			bool classIsMockable = t.GetClassIsMockable();

			// Determine if the current method call is mockable

			if (classIsMockable || (attrs != null && attrs.Length > 0))
			{
				bool noMock = false;

				// Check the method for the DoNotMockMe attribute
				if (attrsDoNotMock != null && attrsDoNotMock.Length > 0)
				{
					foreach (DoNotMockMeAttribute doNotMock in attrsDoNotMock)
					{
						if (doNotMock.DoNotMock)
						{
							noMock = true;
							break;
						}
					}
				}

				RecordParametersAttribute attr;
				int count = classIsMockable ? 1 : 0;
				int total = (attrs == null) ? 0 : attrs.Length;

				for (int n = 0; n < total + count; n++)
				{
					// If the CLASS is marked with the ClassMethodsMockable attribute,
					// consider the method for recording/playback...
					attr = (classIsMockable && n == total) ? RecordParamForMocking : attrs[n];

					// unless the method is marked with the DoNotMockMe attribute
					if (!noMock)
					{
						// Replaying mock objects?  If so, set the return value to the mock object and
						// mark the context to skip execution of the original method
						if (when == CutpointType.Before && attr.AllowMockReplay && MockObjectRepository.MockReplayEnabled)
						{
							UseMockReturnValue(context, t);
						}

						// Replaying mock objects?  Afterwards, re-enable execution of the original method
						if (when == CutpointType.After && attr.AllowMockReplay && MockObjectRepository.MockReplayEnabled && context.ExtraInfo != null)
						{
							ReEnableMethodExecution(context);
						}

						// Recording parameters?
						if (when == CutpointType.After && (!attr.OnlyWhenRecordingMocks || MockObjectRepository.MockRecordingEnabled))
						{
							RecordParametersToMock(context, t, attr, returnValue);
						}
					}

					MethodInfo methodBefore = attr.GetMethodBefore(t);
					MethodInfo methodAfter = attr.GetMethodAfter(t);

					// Optionally execute programmer-specified before and after methods when the method is called
					if (when == CutpointType.Before)
						methodBefore.InvokeIfNotNull(thisObject, context);
					else
						methodAfter.InvokeIfNotNull(thisObject, context, returnValue);
				}
			}
		}
	}
}

